{$mode Delphi}

{
  $Id: datafile.pas 4 2006-10-05 09:18:53Z kcynarski $
  $HeadURL: http://isbuilder.googlecode.com/svn/branches/unify_modules/datafile.pas $
  Inno Setup Builder
  Basic data clasesses unit
}

unit datafile;

interface

uses sysutils, DOM, classes, xmlread, commonfunc, xmlcfg;

type EVersionException = class(Exception);

type TDataModule = class
     private
       fRootNode: TDOMNode;
       fDefines: TStringList;
       fRequirements: TStringList;
       fCID: string;
       {--}
       procedure AddDefine( node: TDOMNode );
       procedure AddRequirement ( node: TDOMNode );
       procedure ProcessNode( node: TDOMNode; nodename: string ); virtual;
       function GetRequirement ( index: integer ): string;
       function GetDefine( index: integer ): string; overload;
       function GetDefine( cid: string ): string; overload;
       function GetDefineCount: integer;
     public
       constructor Create( rootnode: TDOMNode );
       destructor Destroy; override;
       {--}
       function ExtraDefinesProlog: string; virtual;
       function ExtraDefinesEpilog: string; virtual;
       {--}
       property RootNode: TDOMNode read fRootNode;
       property CID: string read fCID;
       property Defines: TStringList read fDefines;
       property DefinesByIndex[ index: integer ]: string read GetDefine; 
       property DefinesByCID[ cid: string ]: string read GetDefine;
       property DefinesCount: integer read GetDefineCount;
       property Requirements: TStringList read fRequirements;
       property RequirementByIndex[ index: integer ]: string read GetDefine;
     end;


type TDataFile = class
     private
       fXML: TXMLDocument;
       fFileName: string;
       fElements: TList;
       fRootNode: TDOMNode;
       fDefines: TStringList;
       fDefinesFile: TStringList;
       {--}
       function GetElement( index: integer ): TDataModule; overload;
       function GetElement( cid: string ): TDataModule; overload;
       function GetElementCount: integer; 
       procedure ProcessNode( node: TDOMNode; nodename: string );
       procedure AddDefine( node: TDOMNode ); overload;
       procedure AddDefine( CID, value: string); overload;
       procedure AddDefine( RAWvalue: string ); overload;
       procedure AddModule( node: TDOMNode );
     public
       constructor Create( filename: string );
       procedure ProcessContent;
       procedure ProcessDefineF(cid: string);
       procedure ProcessDefine(cid: string; AModulesLimit: TStringList; firstfollow: boolean);
       procedure ProcessDefines(ABuild: string; AVariant: string; AModulesLimit: TStringList);
       procedure ProcessDefinesFile(filename: string);
       procedure LoadVariables(filename: string);
       procedure SaveVariables(filename: string);
       destructor Destroy; override;
       {--} 
       property FileName: string read fFileName;
       property ElementByIndex[ no: integer ]: TDataModule read GetElement;
       property ElementByCID[ cid: string ]: TDataModule read GetElement;
       property ElementCount: integer read GetElementCount;
     end;
     
implementation
     
{-- TDataCommonElement --}

constructor TDataModule.Create( rootnode: TDOMNode );
begin
  fRootNode := rootnode;
  fCID := UpCase(XMLGetAttr(rootnode, 'cid', '*ERR_NO_MODULE_CID*'));
  fDefines := TStringList.Create;
  fRequirements := TStringList.Create;
  {--}
  if rootnode.FirstChild <> nil then
     ProcessNode( rootnode.FirstChild, rootnode.NodeName );
end;


function TDataModule.ExtraDefinesProlog: string;
begin
  result:=Format('; [%s]',[fCID]);
end;

function TDataModule.ExtraDefinesEpilog: string;
begin
  result:='';
end;


procedure TDataModule.AddDefine( node: TDOMNode );
begin
  fDefines.Values[ UpCase(XMLGetAttr(node,'key','*ERR_NO_DEF_KEY*')) ] := XMLGetAttr(node,'value','*DEFINED*');
  Debug('Added define %s (%s)',[ UpCase(XMLGetAttr(node,'key','*ERR_NO_DEF_KEY*')),XMLGetAttr(node,'value','*DEFINED*')]);
end;

procedure TDataModule.AddRequirement( node: TDOMNode );
begin
  fRequirements.Add( UpCase(XMLGetAttr(node, 'type', '*ERR_NO_REQ_TYPE*') + '_' + 
    XMLGetAttr(node, 'cid', '*ERR_NO_REQ_CID*'))); 
  Debug('Added requirement %s - %s',[ 
    UpCase(XMLGetAttr(node, 'type', '*ERR_NO_REQ_TYPE*')), 
    XMLGetAttr(node, 'cid', '*ERR_NO_REQ_CID*')
    ]);
end;


function TDataModule.GetRequirement ( index: integer ): string;
begin
  result := fRequirements[index];
end;

function TDataModule.GetDefine( index: integer ): string; overload;
begin
  result := fDefines.Values[fDefines.Names[index]];
end;

function TDataModule.GetDefine( cid: string ): string; overload;
begin
  result := fDefines.Values[UpCase(cid)];
end;

function TDataModule.GetDefineCount: integer;
begin
  result := fDefines.Count;
end;

procedure TDataModule.ProcessNode( node: TDOMNode; nodename: string );
begin
  {--}
  if node.NodeName = 'define' then AddDefine( node );
  if node.NodeName = 'require' then AddRequirement( node );
  {--}
  if node.NextSibling <> nil then
    ProcessNode(node.NextSibling,nodename);
end;

destructor TDataModule.Destroy;
begin
  fDefines.Free;
  fRequirements.Free;
end;

{-- TDataFile --}
     
constructor TDataFile.Create( filename: string );
begin
  Notify('Initializing Engine...');
  fFileName := filename;
  fDefines  := TStringList.Create;
  fDefinesFile := TStringList.Create;
  fElements := TList.Create;
  fRootNode := nil;
  {--}
  Notify('Reading %s data file...',[filename]);
  ReadXMLFile(fXML, filename);
end;

procedure TDataFile.AddDefine( node: TDOMNode );
begin
  AddDefine( UpCase(XMLGetAttr(node,'key','*ERR_NO_DEF_KEY*')) , XMLGetAttr(node,'value','*DEFINED*') );
  Debug('Added define %s (%s)',[UpCase(XMLGetAttr(node,'key','*ERR_NO_DEF_KEY*')),XMLGetAttr(node,'value','*DEFINED*')]);
end;

procedure TDataFile.AddDefine( CID, value: string );
var i:integer;
begin
  if value='' then value:='*DEFINED*';
  {--}
  for i:=0 to fDefines.Count-1 do
    begin
      // Brute force definition replacement - to be changed later;
      value:=StringReplace(value,'{#'+UpCase(fDefines.Names[i])+'}',
        fDefines.Values[fDefines.Names[i]],[rfReplaceAll]);
      try
      value:=StringReplace(value,'{#'+UpCase(fDefines.Names[i])+'+}',
        IntToStr(StrToInt(fDefines.Values[fDefines.Names[i]])+1),[rfReplaceAll]);
      except
        on E:Exception do begin end;
      end;
    end;
  {--}
  fDefines.Add(UpCase(CID)+'='+value);
end;

procedure TDataFile.AddDefine( RAWvalue: string );
begin
  fDefines.Add(RAWvalue);
end;

procedure TDataFile.AddModule( node: TDOMNode );
begin
  fElements.Add( TDataModule.Create( node ) );
end;

procedure TDataFile.ProcessNode( node: TDOMNode; nodename: string );
var NoFollow: boolean;
begin
  NoFollow := false;
  {--}
  if node.NodeName = 'isb' then 
     if fRootNode = nil then fRootNode := node;
  {--}
  if nodename = 'isb' then
     begin
       
       if node.NodeName = 'define' then AddDefine( node );
       if node.NodeName = 'module' then
          begin
           AddModule( node );
           NoFollow := true;
          end;
     end;
  {--}
  if (node.FirstChild <> nil)and(not NoFollow) then
    ProcessNode(node.FirstChild,node.NodeName);
  if node.NextSibling <> nil then
    ProcessNode(node.NextSibling,nodename);
end;

procedure TDataFile.ProcessContent;
begin
  ProcessNode(fXML,'isb'); 
end;

procedure TDataFile.ProcessDefineF(cid: string);
var i:integer;
begin
  for i:=0 to ElementByCID[cid].DefinesCount-1 do
    begin
      AddDefine(ElementByCID[cid].Defines.Names[i],ElementByCID[cid].DefinesByIndex[i]);
    end;
  {--}
  fDefinesFile.Add(ElementByCID[cid].ExtraDefinesProlog);
  for i:=0 to fDefines.Count-1 do
    begin
      //if ElementByCID[cid].Defines.Values[fDefines.Names[i]]<>'' then
      if fDefines.Values[fDefines.Names[i]] = '*DEFINED*' then
         fDefinesFile.Add('#DEFINE '+fDefines.Names[i])
         else
         fDefinesFile.Add('#DEFINE '+fDefines.Names[i]+' '''+fDefines.Values[fDefines.Names[i]]+''''); //** DEBUG **
    end;
  if XMLGetAttr(ElementByCID[cid].RootNode,'script','*NONE*')<>'*NONE*' then
     fDefinesFile.Add('#INCLUDE "'+XMLGetAttr(ElementByCID[cid].RootNode,'script','*NONE*')+'"');
  fDefinesFile.Add(ElementByCID[cid].ExtraDefinesEpilog);
end;

procedure TDataFile.ProcessDefine(cid: string; AModulesLimit: TStringList; firstfollow: boolean);
var i: integer;
begin
  if not firstfollow then ProcessDefineF(cid);
  {--}
  Debug('Processing defines in %s',[cid]);
  if ElementByCID[cid].Requirements.Count>0 then
    for i:=0 to ElementByCID[cid].Requirements.Count-1 do
      if (AModulesLimit<>nil) then
        begin
          if (AModulesLimit.IndexOf(UpCase(cid))>-1) then //Must be on module limit list
            ProcessDefine(UpCase(ElementByCID[cid].Requirements[i]),AModulesLimit,true);
        end
        else //If module limit list is NIL process every sub module
          ProcessDefine(UpCase(ElementByCID[cid].Requirements[i]),AModulesLimit,true);          
  {--}
  if firstfollow then ProcessDefineF(cid);
end;

procedure TDataFile.ProcessDefines(ABuild: string; AVariant: string; AModulesLimit: TStringList);
var i: integer;
begin
  fDefinesFile.Clear; // Prepare file output to be updated
  {--}
  ProcessDefine(UpCase('build_'+ABuild),AMOdulesLimit,true);
  ProcessDefine(UpCase('variant_'+AVariant),AModulesLimit,false);  
  {--}
  fDefinesFile.Add('; Global');
  if XMLGetAttr(fRootNode,'script','*NONE*')<>'*NONE*' then
     fDefinesFile.Add('#INCLUDE "'+XMLGetAttr(fRootNode,'script','*NONE*')+'"');
  {--}
  for i:=0 to fDefines.Count-1 do //** DEBUG **
   writeln(fDefines[i]);
end;

procedure TDataFile.ProcessDefinesFile(filename: string);
begin  
  Notify('Storing defines in include file %s ...',[UpCase(filename)]);
  fDefinesFile.SaveToFile(filename);
end;

procedure TDataFile.LoadVariables(filename: string);
var v:TStringList;
begin
  Notify('Loading variables from file %s ...',[UpCase(filename)]);
  if FileExists(filename) then
    begin
      v := TStringList.Create;
      v.LoadFromFile(filename);
      fDefines.AddStrings(v);
      v.Free;
    end
    else
      Notify('Variable file (%s) not extists. Assuming defaults.',[UpCase(filename)]);  
end;

procedure TDataFile.SaveVariables(filename: string);
var i:integer;
    v:TStringList;
begin
  Notify('Storing variables in file %s ...',[UpCase(filename)]);
  v := TStringList.Create;
  for i:=0 to fDefines.Count-1 do
    if fDefines[i][1]='%' then
      v.Values[fDefines.Names[i]]:=fDefines.Values[fDefines.Names[i]];
  v.SaveToFile(filename);
  v.Free;
end;


destructor TDataFile.Destroy;
var i:integer;
begin
  Debug('Freeing defines...');
  fDefines.Free;
  fDefinesFile.Free;
  {--}
  Debug('Freeing elements...');
  for i:=0 to fElements.Count-1 do 
    if Assigned(fElements[i]) then
       TDataModule(fElements[i]).Free;
  fElements.Free;
  {--}
  Debug('Freeing main XML...');
  fXML.Free;  
  Debug('Done...');
end;

function TDataFile.GetElement( index: integer ): TDataModule;
begin
  result := TDataModule(fElements[index]);
end;

function TDataFile.GetElement( cid: string ): TDataModule;
var i:integer;
begin
  for i:=0 to fElements.Count-1 do
   begin
    if UpCase(TDataModule(fElements[i]).CID) = UpCase(cid) then 
       result := TDataModule(fElements[i]);
   end;
end;

function TDataFile.GetElementCount: integer;
begin
  result := fElements.Count;
end;


end.